﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Nini.Config;
using Ares.Engine;
using System.IO;
using System.Reflection;

namespace Ares.Clr
{
    public class BackupStorageComparer : IEqualityComparer<String>
    {
        public bool Equals(String x, String y)
        {
            return (x.ToLower() == y.ToLower());
        }

        public int GetHashCode(String obj)
        {
            return obj.GetHashCode();
        }
    }

    static class Code
    {

        /// <summary>
        /// Renders a message in the console window that overwrites
        /// any previous message in the same location
        /// </summary>
        /// <param name="message">Message to be displayed in the console</param>
        public static void OverwriteConsoleMessage(string message)
        {
            //move the cursor to the beginning of the console
            Console.CursorLeft = 0;
            //Get size of console
            int maxCharacterWidth = Console.WindowWidth - 1;
            if (message.Length > maxCharacterWidth)
            {
                //if message is longer than the console window truncate it
                message = message.Substring(0, maxCharacterWidth - 3) + "...";
            }
            //create a new message string with the end padded out with blank spaces
            message = message + new string(' ', maxCharacterWidth - message.Length);
            //update the console with the message
            Console.Write(message);
        }

        /// <summary>
        /// Draws a progress bar in a console window using a default character
        /// and maintaining the console window foreground color
        /// </summary>
        /// <param name="percentage">Percentage to be displayed on console</param>
        public static void RenderConsoleProgress(int percentage)
        {
            //other good progress bar characters \u2591 \u2592
            RenderConsoleProgress(percentage, '\u2590', Console.ForegroundColor, "");
        }

        /// <summary>
        /// Draws a progress bar in a console window using a selected character
        /// to make up the progres bar elements. A message can be displayed below
        /// the console at the same time.
        /// </summary>
        /// <param name="percentage">Percentage to be displayed on console</param>
        /// <param name="progressBarCharacter">Character used to build progress bar</param>
        /// <param name="color">Color of progress bar</param>
        /// <param name="message">Message to be displayed below console</param>
        public static void RenderConsoleProgress(int percentage, char progressBarCharacter,
                  ConsoleColor color, string message)
        {
            Console.CursorVisible = false;
            //record the original console color before changing it
            ConsoleColor originalColor = Console.ForegroundColor;
            //set the console to use the selected color
            Console.ForegroundColor = color;
            //move the cursor to the left of the console
            Console.CursorLeft = 0;
            //Determine the maximum width of the console window
            int width = Console.WindowWidth - 1;
            //Calculate the number of character required to create the
            //progress bar
            int newWidth = (int)((width * percentage) / 100d);

            //Create the progress bar text to be displayed
            string progBar = new string(progressBarCharacter, newWidth) +
                  new string(' ', width - newWidth);

            Console.Write(progBar);
            if (string.IsNullOrEmpty(message)) message = "";
            //move the cursor down one line to display the message
            Console.CursorTop++;
            //Render the message below the progress bar
            OverwriteConsoleMessage(message);
            //reset the cursor up 1 line
            Console.CursorTop--;
            //reset the console color back to the original color
            Console.ForegroundColor = originalColor;

            Console.CursorVisible = true;
        }
    }

    public class BackupProgram
    {
        private ArgvConfigSource source;
        private Backup backup = new Backup();
        private bool projectloaded = false;

        public void Run(string[] args)
        {
            source = new ArgvConfigSource(args);

            SetUpSwitches();

            //backup.OnBackupProgressHandler += new BackupProgressHandler(backup_OnBackupProgressHandler);
            backup.OnAddProgressHandler += new AddProgressHandler(backup_OnAddProgressHandler);
            backup.OnSaveProgressHandler += new SaveProgressHandler(backup_OnSaveProgressHandler);
            backup.OnCalculationProgressHandler += new CalculationProgressHandler(backup_OnCalculationProgressHandler);

            if (args.Length > 0)
            {
                //Do we need to create a new project?
                if (source.Configs["Project_Action"].Contains("create"))
                {
                    DoCreateNewProject();
                }
                //Just a backup?
                else if ((source.Configs["Project"].Contains("src") && source.Configs["Project"].Contains("dest"))
                            || (source.Configs["Project"].Contains("proj")))
                {
                        DoLoadProject(true, source.Configs["Project"].GetString("proj"));

                        //Do we need to do some reverts?
                        if (source.Configs["Project_Action"].Contains("rev"))
                        {
                            RevertProject(source.Configs["Project_Action"].GetString("rev"), source.Configs["Project_Action"].GetString("revpath"));
                        }
                        else
                        {
                            DoBackup();
                        }
                }
            }
            else
            {
                WriteApplicationHeader();
                do
                {

                    switch (DrawMenu())
                    {
                        case 1:
                            DoCreateNewProject();
                            break;

                        case 2:
                            DoLoadProject(false, String.Empty);
                            break;

                        case 3:
                            RevertProject(String.Empty, String.Empty);
                            break;

                        case 4:
                            DoBackup();
                            break;

                        case 5:
                            DisplayHelpDialog();
                            break;

                        case 6:
                            Environment.Exit(1);
                            break;
                    }
                } while (true);
            }
        }

        void backup_OnCalculationProgressHandler(object sender, CalculationProgressEvent e)
        {
            long percent = 0;
            if ((e.Progress > 0) && (e.Total > 0))
            {
                percent = Convert.ToInt64(Math.Floor(((Convert.ToDouble(e.Progress) / Convert.ToDouble(e.Total)) * 100.0)));
            }
            Console.WriteLine(String.Format("File {0} of {1}", e.Progress, e.Total));
        }

        void backup_OnSaveProgressHandler(object sender, SaveProgressEvent e)
        {
            long percent = 0;
            if ((e.Progress > 0) && (e.Total > 0))
            {
                percent = Convert.ToInt64(Math.Floor(((Convert.ToDouble(e.Progress) / Convert.ToDouble(e.Total)) * 100.0)));
            }
            Code.RenderConsoleProgress(Convert.ToInt32(percent), '\u2592', ConsoleColor.Blue, String.Format("{0}, {1} of {2}", e.StatusText, CleanSize(Convert.ToInt32(e.Progress)),CleanSize(Convert.ToInt32(e.Total))));                       
        }

        void backup_OnAddProgressHandler(object sender, AddProgressEvent e)
        {
            long percent = 0;
            if ((e.Progress > 0) && (e.Total > 0))
            {
                percent = Convert.ToInt64(Math.Floor(((Convert.ToDouble(e.Progress) / Convert.ToDouble(e.Total)) * 100.0)));
            }
            Code.RenderConsoleProgress(Convert.ToInt32(percent), '\u2592', ConsoleColor.Green, String.Format("{0}, File {1} of {2}", e.StatusText, e.CurrentFileNo, e.TotalFileCount));           
        }


        private static string CleanSize(int Value)
        {
            string[] suf = { "B", "KB", "MB", "GB", "TB", "PB" };
            string readable = String.Empty;
            if (Value != 0)
            {
                int place = Convert.ToInt32(Math.Floor(Math.Log(Value, 1024)));
                double num = Math.Round(Value / Math.Pow(1024, place), 2);
                readable = num.ToString() + suf[place];
            }
            else
            {
                readable = "0";
            }


            return readable;
        }

        /// <summary>
        /// Draw a progress bar at the current cursor position.
        /// Be careful not to Console.WriteLine or anything whilst using this to show progress!
        /// </summary>
        /// <param name="progress">The position of the bar</param>
        /// <param name="total">The amount it counts</param>
        private static void drawTextProgressBar(long progress, long total, bool DoBytesConversion, string StatusText)
        {
            //draw empty progress bar
            Console.CursorLeft = 0;
            Console.Write("["); //start
            Console.CursorLeft = 32;
            Console.Write("]"); //end
            Console.CursorLeft = 1;
            float onechunk = 30.0f / total;

            //draw filled part
            int position = 1;
            for (int i = 0; i < onechunk * progress; i++)
            {
                Console.BackgroundColor = ConsoleColor.Gray;
                Console.CursorLeft = position++;
                Console.Write(" ");
            }

            //draw unfilled part
            for (int i = position; i <= 31; i++)
            {
                Console.BackgroundColor = ConsoleColor.Black;
                Console.CursorLeft = position++;
                Console.Write(" ");
            }

            //draw totals
            Console.CursorLeft = 35;
            Console.BackgroundColor = ConsoleColor.Black;

            if (DoBytesConversion)
            {
                //Awesome:
                Console.Write(CleanSize(Convert.ToInt32(progress)) + " of " + CleanSize(Convert.ToInt32(total)) + "    "); 
            }
            else
            {
                Console.Write(progress.ToString() + " of " + total.ToString() + "    "); 
            }

            //blanks at the end remove any excess
        }

        //void backup_OnBackupProgressHandler(object sender, BackupProgressEvent e)
        //{
        //    long percent = 0;
        //    if ((e.Progress > 0) && (e.Total > 0))
        //    {
        //        percent = Convert.ToInt64(Math.Floor(((Convert.ToDouble(e.Progress) / Convert.ToDouble(e.Total)) * 100.0)));
        //    }
        //    Code.RenderConsoleProgress(Convert.ToInt32(percent), '\u2592', ConsoleColor.Green, String.Format("{0} {1} of {2}\nFile {3} of {4}",e.StatusText, CleanSize(Convert.ToInt32(e.Progress)),CleanSize(Convert.ToInt32(e.Total)), e.CurrentFileNo, e.TotalFileCount) );
        //}

        private void DoBackup()
        {
            Console.WriteLine("You want to run a backup.");
            if (!projectloaded)
            {
                DoLoadProject(false, String.Empty);
            }
            Console.Clear();
            Console.WriteLine("Starting Backup...");
            backup.DoBackup();
            Console.WriteLine("Done.");
            Console.Clear();
        }

        private void DoLoadProject(bool LoadSilently, string path)
        {
            if (!LoadSilently)
            {
                if (path == String.Empty)
                {
                    Console.WriteLine("Please enter the FULL PATH to the project you'd like to load:");
                    path = Console.ReadLine();
                }

                if (!File.Exists(path))
                {
                    bool filepath = false;

                    //Repeat, until we've got a valid answer.
                    do
                    {
                        Console.WriteLine("The name is invalid. Please enter a valid name or cancel by typing 'C':");
                        path = Console.ReadLine();
                        if (path.ToLower() == "c")
                        {
                            Environment.Exit(-1);
                        }
                        else
                        {
                            filepath = (File.Exists(path));
                        }
                    } while (!filepath);

                }
                Console.WriteLine("Attempting to load the project...");
                backup.LoadBackupProject(path);
                Console.WriteLine("Project loaded.");
                projectloaded = true;
            }
            else
            {
                backup.LoadBackupProject(path);
                projectloaded = true;
            }
        }

        /// <summary>
        /// Creates a new project
        /// </summary>
        private void DoCreateNewProject()
        {
            Console.Clear();
            Console.WriteLine("Please enter the new name of the project:");
            string ProjectName = Console.ReadLine();
            if (ProjectName.Trim() == String.Empty)
            {
                bool projname = false;

                //Repeat, until we've got a valid answer.
                do
                {
                    Console.WriteLine("The name is invalid. Please enter a valid name or cancel by typing 'C':");
                    ProjectName = Console.ReadLine();
                    if (ProjectName.ToLower() == "c")
                    {
                        Environment.Exit(-1);
                    }
                    else
                    {
                        projname = (ProjectName.Trim() == String.Empty);
                    }
                } while (!projname);

            }


            Console.WriteLine("Please provide the path to the backup source:");
            string ProjectSourcePath = Console.ReadLine();
            if (!Directory.Exists(ProjectSourcePath))
            {
                bool projsrcpath = false;
                
                //Repeat, until we've got a valid answer.
                do
                {
                    Console.WriteLine("The given path doesn't exist. Please enter a valid path or cancel by typing 'C':");
                    ProjectSourcePath = Console.ReadLine();
                    if (ProjectSourcePath.ToLower() == "c")
                    {
                        Environment.Exit(-1);
                    }
                    else
                    {
                        projsrcpath = Directory.Exists(ProjectSourcePath);
                    }
                } while (!projsrcpath);
                
            }

            Console.WriteLine("Please provide the path to the backup destination:");
            string ProjectDestinationPath = Console.ReadLine();
            if (!Directory.Exists(ProjectSourcePath))
            {
                bool projsrcpath = false;

                //Repeat, until we've got a valid answer.
                do
                {
                    Console.WriteLine("The given path doesn't exist. Please enter a valid path or cancel by typing 'C':");
                    ProjectSourcePath = Console.ReadLine();
                    if (ProjectSourcePath.ToLower() == "c")
                    {
                        Environment.Exit(-1);
                    }
                    else
                    {
                        projsrcpath = Directory.Exists(ProjectSourcePath);
                    }
                } while (!projsrcpath);

            }

            Console.WriteLine("Backup Type [diff|inc|full] :");
            string ProjectType = Console.ReadLine();

            if (ProjectType.ToLower() != "diff" && ProjectType.ToLower() != "inc" && ProjectType.ToLower() != "full")
            {
                bool projtype = false;

                //Repeat, until we've got a valid answer.
                do
                {
                    Console.WriteLine("The given option doesn't exist. Please enter a valid option or cancel by typing 'C':");
                    ProjectType = Console.ReadLine();
                    if (ProjectType.ToLower() == "c")
                    {
                        Environment.Exit(-1);
                    }
                    else
                    {
                        projtype = (ProjectType.ToLower() == "diff" || ProjectType.ToLower() == "inc" || ProjectType.ToLower() == "full");

                    }
                } while (!projtype);

            }
            string StorageEngine ;
            if (backup.GetAllExtensionNames().Count > 1)
            {
                Console.WriteLine("Select a Storage-Plugin. These are the possible names:");
                foreach (string entry in backup.GetAllExtensionNames())
                {
                    Console.WriteLine("\"" + entry + "\"");
                }
                Console.WriteLine("Your Choice:");
                StorageEngine = Console.ReadLine();

                //Wrong entry?
                if (!backup.GetAllExtensionNames().Contains(StorageEngine, new BackupStorageComparer()))
                {
                    bool projstorage = false;

                    //Repeat, until we've got a valid answer.
                    do
                    {
                        Console.WriteLine("The given storage engine doesn't exist. Please enter a valid option or cancel by typing 'C':");
                        StorageEngine = Console.ReadLine();
                        if (StorageEngine.ToLower() == "c")
                        {
                            Environment.Exit(-1);
                        }
                        else
                        {
                            projstorage = backup.GetAllExtensionNames().Contains(StorageEngine, new BackupStorageComparer());
                        }
                    } while (!projstorage);
                }

            }
            else
            {
                if (backup.GetAllExtensionNames().Count == 0)
                {
                    Console.WriteLine("Error: There is no storage engine!");
                    Console.WriteLine("Please fix this problem. Enter RETURN to close this application");
                    Console.ReadKey();
                    Environment.Exit(-2);
                }
                StorageEngine = backup.GetAllExtensionNames()[0];
                Console.WriteLine("There is only a single storage engine, so I'll take this one: \"" + StorageEngine + "\"");
            }

            //Create a new Project based on the given infos
            backup.SetProjectSource(ProjectSourcePath);
            backup.SetProjectDestination(ProjectDestinationPath);
            backup.SetBackupStorageEngine(StorageEngine);
            switch (ProjectType)
            {
                case "diff":
                    backup.SetProjectType(BackupMethod.differential);
                    break;

                case "full":
                    backup.SetProjectType(BackupMethod.full);
                    break;

                case "inc":
                    backup.SetProjectType(BackupMethod.incremental);
                    break;

                default:
                    break;
            }

            backup.SaveBackupProject(Path.Combine(ProjectDestinationPath, ProjectName + ".xml"));
            Console.WriteLine("Created. The project is saved into the backup folder, it's name is \"" + ProjectName + ".xml" + "\". However, it can be placed anywhere.");
            
            Console.WriteLine(String.Empty);
            Console.WriteLine("Should I create the first backup now? (y/n):");
            string BackupAnswer = Console.ReadLine();

            if ((BackupAnswer.ToLower() == "y") || (BackupAnswer.ToLower() == "yes"))
            {
                backup.DoBackup();
            }
            else
            {
                Console.WriteLine("I'll take this as a \"No\".");
            }
            Console.WriteLine("Well, we're done here. Press a key to exit...");
            
            Console.ReadKey();
        }

        private void RevertProject(string answer, string path)
        {
            Console.Clear();
            Console.WriteLine("You would like to revert.");
            if (!projectloaded)
            {
                DoLoadProject(false, String.Empty);
            }


            if (answer == String.Empty)
            {
                Console.WriteLine(String.Empty);
                Console.WriteLine("There are two options: Revert by id or by time. You may now enter a valid date (including the correct time), or a correct id. If I were you, I would use an id.");
                answer = Console.ReadLine();
            }

            //Got a different restore path here
            if (!String.IsNullOrEmpty(path))
            {
                backup.SetProjectDestination(path);
            }

            backup.DoRevert(Convert.ToUInt32(answer));
        }

        private uint DrawMenu()
        {
            uint choosennumber = 0;
            bool wrongentry = true;
            Console.WriteLine("MENU");
            Console.WriteLine("===========");
            Console.WriteLine(String.Empty);
            Console.WriteLine("[1] Create a new project");
            Console.WriteLine("[2] Load a project");
            Console.WriteLine("[3] Revert to a given state");
            Console.WriteLine("[4] Do Backup");
            Console.WriteLine("[5] Help");
            Console.WriteLine("[6] Quit");
            Console.WriteLine(String.Empty);
            Console.WriteLine("Your choice:");

            do
            {
                if (UInt32.TryParse(Console.ReadLine(), out choosennumber))
                {
                    switch (choosennumber)
                    {
                        case 1:
                        case 2:
                        case 3:
                        case 4:
                        case 5:
                        case 6:
                            return choosennumber;
                        default:
                            wrongentry = true;
                            break;
                    }

                }
                else
                {
                    wrongentry = true;
                }
                Console.WriteLine("Wrong entry, try again:");
            } while (wrongentry);
            return 0;
        }

        private void DisplayHelpDialog()
        {
            Console.WriteLine("+----------------------+");
            Console.WriteLine("|PROJECT SPECIFIC      |");
            Console.WriteLine("+----------------------+");
            Console.WriteLine("");

            string[] headers = new string[] { "Command", "Description" };
            UI_Format ConsoleFormatter = new UI_Format(-1, UI_Format.Align.Left, headers);

            string[][] data = new string[][] {
                new string[] {
                "-src <<source folder>>", "Defines the source folder. Is mandatory,"},
                new string[] {
                String.Empty, "if the -proj argument is omitted."},
                new string[] {
                String.Empty, String.Empty},
                new string[] {
                "-dest <<target folder>>", "Defines the destination folder. Is mandatory,"},
                new string[] {
                String.Empty, "if the -proj argument is omitted."},
                new string[] {
                String.Empty, String.Empty},
                new string[] {
                "-proj <<project path>>", "Defines the project to load."},
                new string[] {
                String.Empty,  "The project path is mandatory."},
                new string[] {
                String.Empty, String.Empty},
                new string[] {
                "-engine <<name|file|guid>>", "Defines the name, file name"},
                new string[] {
                String.Empty,  "or guid of the storage engine to use."},
                new string[] {
                String.Empty, String.Empty},
                new string[] {
                "-type <<diff|inc|full>>", "Defines the type of the project:"},
                new string[] {
                String.Empty,  "-diff: Creates backups that store"},
                new string[] {
                String.Empty,  "       the differences to the last full backup"},
                new string[] {
                String.Empty,  "-inc:  Creates a backup based on"},
                new string[] {
                String.Empty,  "       the backup before."},
                new string[] {
                String.Empty,  "-full: Creates a full backup every time."},
            };

            ConsoleFormatter.RePrint(new ArrayList(data)); //Get data variable from the link in the description

            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("+----------------------+");
            Console.WriteLine("|PROJECT ACTIONS       |");
            Console.WriteLine("+----------------------+");
            Console.WriteLine("");

            UI_Format ConsoleFormatter2 = new UI_Format(-1, UI_Format.Align.Left, headers);

            string[][] data2 = new string[][] {
                new string[] {
                "-create", "Creates a new project."},
                new string[] {
                "", ""},
                new string[] {
                "-rev <<id|date>>", "Reverts the project back to the given ID"},
                new string[] {
                "", "or the given date."}
            };

            ConsoleFormatter2.RePrint(new ArrayList(data2)); //Get data variable from the link in the description

            Console.ReadKey();
        }

        private void WriteApplicationHeader()
        {
            Console.WriteLine("+-----------------------------+");
            Console.WriteLine("|     BackUp!  --   v.01      |");
            Console.WriteLine("|         01.02.2012          |");
            Console.WriteLine("|                             |");
            Console.WriteLine("| (C) Daniel Gilbert, 2012    |");
            Console.WriteLine("+-----------------------------+");
        }

        private void SetUpSwitches()
        {
            source.AddSwitch("Project", "src");
            source.AddSwitch("Project", "dest");
            source.AddSwitch("Project", "proj");

            source.AddSwitch("Project_Action", "rev");
            source.AddSwitch("Project_Action", "revpath");
            source.AddSwitch("Project_Action", "create");

            source.AddSwitch("Project", "type");

            source.AddSwitch("Project", "engine");
        }
    }
}